package geradores;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

import util.ArquivoUtil;
import analisadorSemantico.Simbolo;

public class GeradorCodigoObjetoAnalisadorLexico {

	private PrintWriter arquivoSaidaAnalisadorSintatico;

	private static String nomeArquivoSaida = "AnalisadorLexico";

	private static String caminhoArquivo = "c:\\gerador\\saida\\";

	private BufferedReader regrasGramatica;

	private List<Simbolo> terminaisClasse = new ArrayList<>();

	private HashMap<String, String> regrasPadraoToken = new HashMap<String, String>();

	/**
	 * Construtor
	 * 
	 * @param regrasGramatica
	 * @param terminalClasse
	 */
	public GeradorCodigoObjetoAnalisadorLexico(BufferedReader regrasGramatica, List<Simbolo> terminalClasse) {
		this.regrasGramatica = regrasGramatica;
		this.terminaisClasse = terminalClasse;
		try {
			this.arquivoSaidaAnalisadorSintatico = ArquivoUtil.criarArquivo(caminhoArquivo, nomeArquivoSaida + ".java");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}

	}

	/**
	 * Método Para Começar a Construir o Analisador Léxico
	 */

	public void gerarAnalisadorLexico() {
		try {
			lerArquivoInfo();
		} catch (IOException e) {
			e.printStackTrace();
		}
		gerarCodigoPadrao();
		gerarMetodoAnalisar();
		ArquivoUtil.fecharArquivo(arquivoSaidaAnalisadorSintatico);

	}

	/**
	 * Método para ler o arquivo de informações referentes ao terminais de
	 * classe
	 * 
	 * @throws IOException
	 */
	private void lerArquivoInfo() throws IOException {

		String linhaArquivo;

		do {
			linhaArquivo = regrasGramatica.readLine();

			if (linhaArquivo != null) {

				StringTokenizer stringTokenizer = new StringTokenizer(linhaArquivo, "=");

				while (stringTokenizer.hasMoreTokens()) {

					String parteEsquerda = stringTokenizer.nextToken().trim();
					String parteDireita = stringTokenizer.nextToken().trim();

					regrasPadraoToken.put(parteEsquerda, parteDireita);

				}
			}
		} while (linhaArquivo != null);

	}

	/**
	 * Método para gerar o código padrão Imports, gets e sets and construtores
	 */
	private void gerarCodigoPadrao() {

		// INICIO IMPORTS

		ArquivoUtil.escreverArquivo("// INICIO IMPORTS\n\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("import java.io.BufferedReader; \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("import java.io.IOException; \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("import java.util.ArrayList; \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("import java.util.List; \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("import java.util.StringTokenizer; \n\n\n\n", arquivoSaidaAnalisadorSintatico);

		ArquivoUtil.escreverArquivo("public class " + nomeArquivoSaida + " { \n\n", arquivoSaidaAnalisadorSintatico);

		// INICIO VARIAVEIS
		ArquivoUtil.escreverArquivo("// INICIO VARIAVEIS \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("private List<Token> tokens = new ArrayList<Token>(); \n",
				arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("private List<String> erros = new ArrayList<String>(); \n",
				arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("private BufferedReader br; \n\n\n", arquivoSaidaAnalisadorSintatico);

		// INICIO CONSTRUTOR
		ArquivoUtil.escreverArquivo("// INICIO CONSTRUTOR\n\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("public " + nomeArquivoSaida + " (BufferedReader br) { \n\n",
				arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t this.br = br; \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("} \n\n", arquivoSaidaAnalisadorSintatico);

		// MÉTODO GET TOKEN
		ArquivoUtil.escreverArquivo("// MÉTODO GET TOKEN\n\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("public List<Token> getTokens() { \n\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t return tokens; \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("} \n\n", arquivoSaidaAnalisadorSintatico);

		// INICIO MÉTODO MOSTRAR TOKENS

		ArquivoUtil.escreverArquivo("// INICIO MÉTODO MOSTRAR TOKENS\n\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("public void mostrarToken() { \n\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t for (Token token : tokens) { \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\t System.out.println(token); \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t } \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("} \n\n ", arquivoSaidaAnalisadorSintatico);

		// INICIO MÉTODO MOSTRAR ERROS
		ArquivoUtil.escreverArquivo("// INICIO MÉTODO MOSTRAR ERROS\n\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("public void mostrarErros() { \n\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t for (String erro : erros) { \n ", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\t System.out.println(erro);\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t } \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("}\n\n", arquivoSaidaAnalisadorSintatico);

	}

	/**
	 * Método para gerar o método Analisar no analisador léxico
	 */
	private void gerarMetodoAnalisar() {

		// INICIO MÉTODO ANALISAR
		ArquivoUtil.escreverArquivo("// INICIO MÉTODO ANALISAR \n\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("public boolean analisar() throws IOException { \n\n ",
				arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\tString linha; \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\tString imagem; \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\tInteger nLinha = 0; \n\n", arquivoSaidaAnalisadorSintatico);

		ArquivoUtil.escreverArquivo("\tdo { \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\t linha = br.readLine(); \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\t\t if (linha != null) {\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\t\t\tnLinha = nLinha + 1;\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\t\t\tStringTokenizer st = new StringTokenizer(linha, \" \" );\n",
				arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\t\t\twhile (st.hasMoreTokens()) { \n", arquivoSaidaAnalisadorSintatico);

		ArquivoUtil.escreverArquivo("\t\t\t\t\timagem = st.nextToken().trim(); \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\t\t\t\t\tif (!imagem.equals(\"\")) { \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\t\t\t\t\tif (imagem.startsWith(\"#\")) { \n",
				arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\t\t\t\t\t\tbreak; \n", arquivoSaidaAnalisadorSintatico);

		ArquivoUtil.escreverArquivo("\t\t\t\t\t\t} else if (Tabela.ehPalavraReservada(imagem)) { \n",
				arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo(
				"\t\t\t\t\t\t\tToken token = new Token(imagem, Classe.PALAVRA_RESERVADA, nLinha, null, null); \n",
				arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\t\t\t\t\t\ttokens.add(token); \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\t\t\t\t\t} ", arquivoSaidaAnalisadorSintatico);

		gerarArquivoEntradaInfo();

		ArquivoUtil.escreverArquivo("else { \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil
				.escreverArquivo(
						"\t\t\t\t\t\t\t erros.add(\"Erro Fatal na linha \" + nLinha + \" Token Não Reconhecido \" + imagem); \n",
						arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\t\t\t\t\t} \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\t\t\t } \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\t\t\t } \n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\t\t\t\t\t}\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t} while (linha != null);\n\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\tToken token = new Token(\"$\", Classe.EOF, nLinha, 0, 0);\n",
				arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\ttokens.add(token);\n\n", arquivoSaidaAnalisadorSintatico);

		ArquivoUtil.escreverArquivo("\tif (erros.size() > 0) {\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\treturn false;\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t} else {\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t\treturn true;\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("\t}\n", arquivoSaidaAnalisadorSintatico);		
		ArquivoUtil.escreverArquivo("}\n\n\n", arquivoSaidaAnalisadorSintatico);
		ArquivoUtil.escreverArquivo("}\n", arquivoSaidaAnalisadorSintatico);

		ArquivoUtil.fecharArquivo(arquivoSaidaAnalisadorSintatico);
	}

	/**
	 * Método para gerar os IF baseado no arquivo de terminais de classe
	 */
	private void gerarArquivoEntradaInfo() {

		Set<String> chaves = this.regrasPadraoToken.keySet();
		for (String chave : chaves) {
			if (chave != null)

				ArquivoUtil.escreverArquivo(
						"\t\t else if (imagem.matches(\"" + buscarStringPadrao(chave) + "\")) { \n",
						arquivoSaidaAnalisadorSintatico);

			ArquivoUtil.escreverArquivo("\t\t\tToken token = new Token(imagem, Classe." + chave.trim().toUpperCase()
					+ ", nLinha, null, null); \n", arquivoSaidaAnalisadorSintatico);

			ArquivoUtil.escreverArquivo("\t\t\ttokens.add(token); \n", arquivoSaidaAnalisadorSintatico);

			ArquivoUtil.escreverArquivo("}", arquivoSaidaAnalisadorSintatico);
		}
	}

	/**
	 * @param imagem
	 * @return
	 */
	private String buscarStringPadrao(String imagem) {
		return this.regrasPadraoToken.get(imagem);
	}

	/**
	 * Gets and Sets
	 */
	public List<Simbolo> getTerminaisClasse() {
		return terminaisClasse;
	}

	public void setTerminaisClasse(List<Simbolo> terminaisClasse) {
		this.terminaisClasse = terminaisClasse;
	}

	public HashMap<String, String> getRegrasInfo() {
		return regrasPadraoToken;
	}

	public void setRegrasInfo(HashMap<String, String> regrasInfo) {
		this.regrasPadraoToken = regrasInfo;
	}

}
